package com.geocalc;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * DEM数据读取器类，用于读取ASCII格式的DEM数据文件并进行高程计算
 */
public class DemReader {
    private int ncols;        // 列数
    private int nrows;        // 行数
    private double xllcorner; // 左下角经度
    private double yllcorner; // 左下角纬度
    private double cellsize;  // 单元格大小
    private List<List<Double>> demData; // 存储高程数据的二维列表
    private boolean dataLoaded; // 数据加载状态
    private static final double NODATA_VALUE = -9999.0; // 无效数据标记
    
    /**
     * 构造函数
     */
    public DemReader() {
        this.ncols = 0;
        this.nrows = 0;
        this.xllcorner = 0.0;
        this.yllcorner = 0.0;
        this.cellsize = 0.0;
        this.demData = new ArrayList<>();
        this.dataLoaded = false;
    }
    
    /**
     * 从文件加载DEM数据
     * @param filePath DEM文件路径
     * @return 加载是否成功
     * @throws ElevationException 当加载数据失败时抛出
     */
    public boolean loadDemData(String filePath) throws ElevationException {
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            // 清理之前的数据
            demData.clear();
            dataLoaded = false;
            
            String line;
            String keyword;
            boolean dataSectionStarted = false;
            
            // 读取头部信息
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty()) {
                    continue;
                }
                
                // 如果已经进入数据部分
                if (dataSectionStarted) {
                    processDataLine(line);
                    continue;
                }
                
                // 处理头部信息
                String[] parts = line.split("\\s+");
                keyword = parts[0];
                
                if (keyword.equals("ncols")) {
                    ncols = Integer.parseInt(parts[1]);
                } else if (keyword.equals("nrows")) {
                    nrows = Integer.parseInt(parts[1]);
                } else if (keyword.equals("xllcorner")) {
                    xllcorner = Double.parseDouble(parts[1]);
                } else if (keyword.equals("yllcorner")) {
                    yllcorner = Double.parseDouble(parts[1]);
                } else if (keyword.equals("cellsize")) {
                    cellsize = Double.parseDouble(parts[1]);
                } else if (keyword.equals("NODATA_value")) {
                    // 读取NODATA_value，这里我们知道它是"x"
                    // 继续处理
                } else {
                    // 进入数据部分
                    dataSectionStarted = true;
                    processDataLine(line);
                }
            }
            
            dataLoaded = true;
            System.out.println("成功加载DEM数据: " + ncols + "x" + nrows + " 网格，左下角坐标(" + xllcorner + ", " + yllcorner + ")，单元格大小: " + cellsize);
            return true;
        } catch (IOException e) {
            throw new ElevationException("无法打开DEM数据文件: " + filePath, e);
        } catch (NumberFormatException e) {
            throw new ElevationException("解析DEM数据格式错误: " + e.getMessage(), e);
        }
    }
    
    /**
     * 处理数据行
     * @param line 数据行
     */
    private void processDataLine(String line) {
        String[] parts = line.split("\\s+");
        List<Double> rowData = new ArrayList<>(ncols);
        
        for (String part : parts) {
            if (part.equals("x")) {
                rowData.add(NODATA_VALUE);
            } else {
                try {
                    rowData.add(Double.parseDouble(part));
                } catch (NumberFormatException e) {
                    System.err.println("解析DEM数据时出错，值: " + part + ", 设为无效值");
                    rowData.add(NODATA_VALUE);
                }
            }
        }
        
        // 检查数据点数量是否符合预期
        if (rowData.size() != ncols) {
            System.err.println("警告: 一行的数据点数量 (" + rowData.size() + ") 与预期不同 (" + ncols + "), 尝试继续...");
            
            // 如果数据点不足，填充NODATA_VALUE
            while (rowData.size() < ncols) {
                rowData.add(NODATA_VALUE);
            }
        }
        
        demData.add(rowData);
        
        // 每100行显示一次进度
        if (demData.size() % 100 == 0) {
            System.out.println("已加载" + demData.size() + "行数据...");
        }
    }
    
    /**
     * 将经纬度转换为网格坐标
     * @param longitude 经度
     * @param latitude 纬度
     * @return 包含col和row的数组，如果坐标不在范围内则返回null
     */
    public int[] latLonToGrid(double longitude, double latitude) {
        // 计算列号和行号
        // 列号 = (经度 - 左下角经度) / 单元格大小
        int col = (int) Math.floor((longitude - xllcorner) / cellsize);
        // 行号 = (左下角纬度 + 行数*单元格大小 - 纬度) / 单元格大小
        // 注意这里行号的计算是倒序的，因为DEM数据通常从上到下存储
        int row = (int) Math.floor((yllcorner + nrows * cellsize - latitude) / cellsize);
        
        // 检查坐标是否在数据范围内
        // 注意需要-1，因为我们需要至少两个点进行双线性插值
        if (col < 0 || col >= ncols - 1 || row < 0 || row >= nrows - 1) {
            System.err.println("坐标(" + longitude + ", " + latitude + ") 不在DEM数据范围内");
            return null;
        }
        
        return new int[] {col, row};
    }
    
    /**
     * 使用双线性插值计算给定坐标的高程值
     * @param longitude 经度
     * @param latitude 纬度
     * @return 计算得到的高程值
     */
    public double bilinearInterpolation(double longitude, double latitude) {
        int[] gridPos = latLonToGrid(longitude, latitude);
        if (gridPos == null) {
            return 0.0;
        }
        
        int col = gridPos[0];
        int row = gridPos[1];
        
        // 计算在单元格中的相对位置
        // xRatio和yRatio表示目标点在网格单元内的相对坐标(0-1之间)
        double xRatio = (longitude - (xllcorner + col * cellsize)) / cellsize;
        double yRatio = (latitude - (yllcorner + (nrows - row - 1) * cellsize)) / cellsize;
        
        // 获取四个角点的高程值
        // z1: 左下角, z2: 右下角, z3: 左上角, z4: 右上角
        double z1 = demData.get(row).get(col);
        double z2 = demData.get(row).get(col + 1);
        double z3 = demData.get(row + 1).get(col);
        double z4 = demData.get(row + 1).get(col + 1);
        
        // 检查是否有无效数据点
        int validPoints = 0;
        double sumZ = 0.0;
        
        if (z1 != NODATA_VALUE) { validPoints++; sumZ += z1; }
        if (z2 != NODATA_VALUE) { validPoints++; sumZ += z2; }
        if (z3 != NODATA_VALUE) { validPoints++; sumZ += z3; }
        if (z4 != NODATA_VALUE) { validPoints++; sumZ += z4; }
        
        // 如果所有点都是无效的，返回0
        if (validPoints == 0) {
            System.err.println("警告: 查询点周围没有有效高程数据");
            return 0.0;
        }
        
        // 如果部分点无效，使用有效点的平均值
        if (validPoints < 4) {
            System.err.println("警告: 查询点周围部分数据无效，使用平均值");
            return sumZ / validPoints;
        }
        
        // 双线性插值计算
        // 双线性插值的公式：
        // z = (1-x) * (1-y) * z1 + x*(1-y)*z2 + (1-x)*y*z3 + x*y*z4
        // 其中x和y是相对位置，z1-z4是四个角点的高程
        double z = (1 - xRatio) * (1 - yRatio) * z1 + 
                  xRatio * (1 - yRatio) * z2 + 
                  (1 - xRatio) * yRatio * z3 + 
                  xRatio * yRatio * z4;
        
        return z;
    }
    
    /**
     * 计算给定经纬度的高程值
     * @param longitude 经度
     * @param latitude 纬度
     * @return 高程值
     * @throws ElevationException 当数据未加载时抛出
     */
    public double calculateElevation(double longitude, double latitude) throws ElevationException {
        if (!dataLoaded || demData.isEmpty()) {
            throw new ElevationException("DEM数据未加载");
        }
        
        // 使用双线性插值计算高程
        return bilinearInterpolation(longitude, latitude);
    }
    
    /**
     * 检查数据是否加载
     * @return 数据加载状态
     */
    public boolean isDataLoaded() {
        return dataLoaded;
    }
    
    /**
     * 获取数据信息
     * @return 包含ncols, nrows, xllcorner, yllcorner, cellsize的数组
     */
    public double[] getDataInfo() {
        return new double[] {ncols, nrows, xllcorner, yllcorner, cellsize};
    }
}